package panda.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.CharArrayWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.vfs2.FileObject;

import panda.lang.Asserts;
import panda.lang.CharSequences;
import panda.lang.Strings;

/**
 * I/O Utilities class.
 * 
 * @author yf.frank.wang@gmail.com
 */
public class Streams {
	/**
	 * End of Stream
	 */
	public static final int EOF = -1;

	/**
	 * The Unix line separator string.
	 */
	public static final String LINE_SEPARATOR_UNIX = Strings.LF;

	/**
	 * The Windows line separator string.
	 */
	public static final String LINE_SEPARATOR_WINDOWS = Strings.CRLF;
	
	/**
	 * The system line separator string.
	 */
	public static final String LINE_SEPARATOR;

	static {
		// avoid security issues
		StringBuilderWriter buf = new StringBuilderWriter(4);
		PrintWriter out = new PrintWriter(buf);
		out.println();
		LINE_SEPARATOR = buf.toString();
		out.close();
	}

	/**
	 * The default buffer size ({@value} ) to use for {@link #copyLarge(InputStream, OutputStream)}
	 * and {@link #copyLarge(Reader, Writer)}
	 */
	private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;

	/**
	 * The default buffer size to use for the skip() methods.
	 */
	private static final int SKIP_BUFFER_SIZE = 2048;

	// Allocated in the relevant skip method if necessary.
	/*
	 * N.B. no need to synchronize these because: - we don't care if the buffer is created multiple
	 * times (the data is ignored) - we always use the same size buffer, so if it it is recreated it
	 * will still be OK (if the buffer size were variable, we would need to synch. to ensure some
	 * other thread did not create a smaller one)
	 */
	private static char[] SKIP_CHAR_BUFFER;
	private static byte[] SKIP_BYTE_BUFFER;

	/**
	 * Copy the contents of the given input File to the given output File.
	 * 
	 * @param in the file to copy from
	 * @param out the file to copy to
	 * @return the number of bytes copied
	 * @throws IOException in case of I/O errors
	 */
	public static int copy(File in, File out) throws IOException {
		InputStream is = null;
		OutputStream os = null;
		
		try {
			is = new FileInputStream(in);
			os = new FileOutputStream(out);
			return copy(is, os);
		}
		finally {
			safeClose(is);
			safeClose(os);
		}
	}

	/**
	 * Copy the contents of the given input File to the given output File.
	 * 
	 * @param in the file to copy from
	 * @param os the output stream to copy to
	 * @return the number of bytes copied
	 * @throws IOException in case of I/O errors
	 */
	public static int copy(File in, OutputStream os) throws IOException {
		InputStream is = null;
		
		try {
			is = new FileInputStream(in);
			return copy(is, os);
		}
		finally {
			safeClose(is);
		}
	}

	/**
	 * Returns a copy of the specified stream. If the specified stream supports marking, it will be
	 * reset after the copy.
	 *
	 * @param sourceStream the stream to copy
	 * @return a copy of the stream
	 */
	public static InputStream copy(InputStream sourceStream) throws IOException {
		if (sourceStream.markSupported())
			sourceStream.mark(sourceStream.available());
		byte[] sourceData = toByteArray(sourceStream);
		if (sourceStream.markSupported())
			sourceStream.reset();
		return new ByteArrayInputStream(sourceData);
	}

	/**
	 * Returns a copy of the specified reader. If the specified reader supports marking, it will be
	 * reset after the copy.
	 *
	 * @param sourceReader the stream to reader
	 * @return a copy of the reader
	 */
	public static Reader copy(Reader sourceReader) throws IOException {
		if (sourceReader.markSupported())
			sourceReader.mark(Integer.MAX_VALUE);
		String sourceData = toString(sourceReader);
		if (sourceReader.markSupported())
			sourceReader.reset();
		return new StringReader(sourceData);
	}

	/**
	 * Write the contents of the given byte array to the given output File.
	 * 
	 * @param in the byte array to copy from
	 * @param out the file to copy to
	 * @throws IOException in case of I/O errors
	 */
	public static void write(byte[] in, File out) throws IOException {
		OutputStream os = null;
		
		try {
			os = new FileOutputStream(out);
			write(in, os);
		}
		finally {
			safeClose(os);
		}
	}

	/**
	 * read url content to byte array
	 * 
	 * @param url url
	 * @return byte array
	 * @throws FileNotFoundException if file not found
	 * @throws IOException in case of I/O errors
	 */
	public static byte[] toByteArray(URL url) throws IOException {
		InputStream is = url.openStream();
		try {
			byte[] b = toByteArray(is);
			return b;
		}
		finally {
			safeClose(is);
		}
	}

	/**
	 * read file content to byte array
	 * 
	 * @param file file
	 * @return byte array
	 * @throws FileNotFoundException if file not found
	 * @throws IOException in case of I/O errors
	 */
	public static byte[] toByteArray(File file) throws FileNotFoundException, IOException {
		FileInputStream fis = new FileInputStream(file);
		try {
			byte[] b = toByteArray(fis);
			return b;
		}
		finally {
			safeClose(fis);
		}
	}

	/**
	 * read file content to byte array
	 * 
	 * @param file file
	 * @return byte array
	 * @throws FileNotFoundException if file not found
	 * @throws IOException in case of I/O errors
	 */
	public static byte[] toByteArray(FileObject file) throws FileNotFoundException, IOException {
		InputStream is = null;
		try {
			is = file.getContent().getInputStream();
			byte[] b = toByteArray(is);
			return b;
		}
		finally {
			safeClose(is);
		}
	}

	/**
	 * Unconditionally close a <code>Closeable</code>.
	 * <p>
	 * Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. This is
	 * typically used in finally blocks.
	 * <p>
	 * Example code:
	 * 
	 * <pre>
	 * Closeable closeable = null;
	 * try {
	 * 	closeable = new FileReader(&quot;foo.txt&quot;);
	 * 	// process closeable
	 * 	closeable.close();
	 * }
	 * catch (Exception e) {
	 * 	// error handling
	 * }
	 * finally {
	 * 	IOUtils.closeQuietly(closeable);
	 * }
	 * </pre>
	 * 
	 * @param closeable the object to close, may be null or already closed
	 */
	public static void safeClose(Closeable closeable) {
		try {
			if (closeable != null) {
				closeable.close();
			}
		}
		catch (IOException ioe) {
			// ignore
		}
	}

	/**
	 * 安全刷新一个可刷新的对象，可接受 null
	 * 
	 * @param fa 可刷新对象
	 */
	public static void safeFlush(Flushable fa) {
		if (null != fa)
			try {
				fa.flush();
			}
			catch (IOException e) {
			}
	}

	/**
	 * Fetches entire contents of an <code>InputStream</code> and represent same data as result
	 * InputStream.
	 * <p>
	 * This method is useful where,
	 * <ul>
	 * <li>Source InputStream is slow.</li>
	 * <li>It has network resources associated, so we cannot keep it open for long time.</li>
	 * <li>It has network timeout associated.</li>
	 * </ul>
	 * It can be used in favor of {@link #toByteArray(InputStream)}, since it avoids unnecessary
	 * allocation and copy of byte[].<br>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * 
	 * @param input Stream to be fully buffered.
	 * @return A fully buffered stream.
	 * @throws IOException if an I/O error occurs
	 */
	public static InputStream toBufferedInputStream(InputStream input) throws IOException {
		return ByteArrayOutputStream.toBufferedInputStream(input);
	}

	/**
	 * Returns the given reader if it is a {@link BufferedReader}, otherwise creates a
	 * toBufferedReader for the given reader.
	 * 
	 * @param reader the reader to wrap or return
	 * @return the given reader or a new {@link BufferedReader} for the given reader
	 */
	public static BufferedReader toBufferedReader(Reader reader) {
		return reader instanceof BufferedReader ? (BufferedReader)reader : new BufferedReader(
			reader);
	}

	// read toByteArray
	// -----------------------------------------------------------------------
	/**
	 * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @return the requested byte array
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static byte[] toByteArray(InputStream input) throws IOException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		copy(input, output);
		return output.toByteArray();
	}

	/**
	 * Get contents of an <code>InputStream</code> as a <code>byte[]</code>. Use this method instead
	 * of <code>toByteArray(InputStream)</code> when <code>InputStream</code> size is known.
	 * <b>NOTE:</b> the method checks that the length can safely be cast to an int without
	 * truncation before using {@link Streams#toByteArray(java.io.InputStream, int)} to read into
	 * the byte array. (Arrays can have no more than Integer.MAX_VALUE entries anyway)
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param size the size of <code>InputStream</code>
	 * @return the requested byte array
	 * @throws IOException if an I/O error occurs or <code>InputStream</code> size differ from
	 *             parameter size
	 * @throws IllegalArgumentException if size is less than zero or size is greater than
	 *             Integer.MAX_VALUE
	 * @see Streams#toByteArray(java.io.InputStream, int)
	 */
	public static byte[] toByteArray(InputStream input, long size) throws IOException {

		if (size > Integer.MAX_VALUE) {
			throw new IllegalArgumentException("Size cannot be greater than Integer max value: "
					+ size);
		}

		return toByteArray(input, (int)size);
	}

	/**
	 * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>. Use this method
	 * instead of <code>toByteArray(InputStream)</code> when <code>InputStream</code> size is known
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param size the size of <code>InputStream</code>
	 * @return the requested byte array
	 * @throws IOException if an I/O error occurs or <code>InputStream</code> size differ from
	 *             parameter size
	 * @throws IllegalArgumentException if size is less than zero
	 */
	public static byte[] toByteArray(InputStream input, int size) throws IOException {

		if (size < 0) {
			throw new IllegalArgumentException("Size must be equal or greater than zero: " + size);
		}

		if (size == 0) {
			return new byte[0];
		}

		byte[] data = new byte[size];
		int offset = 0;
		int readed;

		while (offset < size && (readed = input.read(data, offset, size - offset)) != EOF) {
			offset += readed;
		}

		if (offset != size) {
			throw new IOException("Unexpected readed size. current: " + offset + ", excepted: "
					+ size);
		}

		return data;
	}

	/**
	 * Get the contents of a <code>Reader</code> as a <code>byte[]</code> using the default
	 * character encoding of the platform.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @return the requested byte array
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static byte[] toByteArray(Reader input) throws IOException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		copy(input, output);
		return output.toByteArray();
	}

	/**
	 * Get the contents of a <code>Reader</code> as a <code>byte[]</code> using the specified
	 * character encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @param encoding the encoding to use, null means platform default
	 * @return the requested byte array
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 * @since 1.1
	 */
	public static byte[] toByteArray(Reader input, String encoding) throws IOException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		copy(input, output, encoding);
		return output.toByteArray();
	}

	// read char[]
	// -----------------------------------------------------------------------
	/**
	 * Get the contents of an <code>InputStream</code> as a character array using the default
	 * character encoding of the platform.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * 
	 * @param is the <code>InputStream</code> to read from
	 * @return the requested character array
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static char[] toCharArray(InputStream is) throws IOException {
		CharArrayWriter output = new CharArrayWriter();
		copy(is, output);
		return output.toCharArray();
	}

	/**
	 * Get the contents of an <code>InputStream</code> as a character array using the specified
	 * character encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * 
	 * @param is the <code>InputStream</code> to read from
	 * @param encoding the encoding to use, null means platform default
	 * @return the requested character array
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static char[] toCharArray(InputStream is, String encoding) throws IOException {
		CharArrayWriter output = new CharArrayWriter();
		copy(is, output, encoding);
		return output.toCharArray();
	}

	/**
	 * Get the contents of a <code>Reader</code> as a character array.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @return the requested character array
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static char[] toCharArray(Reader input) throws IOException {
		CharArrayWriter sw = new CharArrayWriter();
		copy(input, sw);
		return sw.toCharArray();
	}

	// read toString
	// -----------------------------------------------------------------------
	/**
	 * Get the contents of an <code>InputStream</code> as a String using the default character
	 * encoding of the platform.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @return the requested String
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static String toString(InputStream input) throws IOException {
		return toString(input, null);
	}

	/**
	 * Get the contents of an <code>InputStream</code> as a String using the specified character
	 * encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param encoding the encoding to use, null means platform default
	 * @return the requested String
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static String toString(InputStream input, String encoding) throws IOException {
		StringBuilderWriter sw = new StringBuilderWriter();
		copy(input, sw, encoding);
		return sw.toString();
	}

	/**
	 * Get the contents of a <code>Reader</code> as a String.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @return the requested String
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static String toString(Reader input) throws IOException {
		StringBuilderWriter sw = new StringBuilderWriter();
		copy(input, sw);
		return sw.toString();
	}

	/**
	 * Gets the contents at the given URI.
	 * 
	 * @param uri The URI source.
	 * @return The contents of the URL as a String.
	 * @throws IOException if an I/O exception occurs.
	 */
	public static String toString(URI uri) throws IOException {
		return toString(uri, null);
	}

	/**
	 * Gets the contents at the given URI.
	 * 
	 * @param uri The URI source.
	 * @param encoding The encoding name for the URL contents.
	 * @return The contents of the URL as a String.
	 * @throws IOException if an I/O exception occurs.
	 */
	public static String toString(URI uri, String encoding) throws IOException {
		return toString(uri.toURL(), encoding);
	}

	/**
	 * Gets the contents at the given URL.
	 * 
	 * @param url The URL source.
	 * @return The contents of the URL as a String.
	 * @throws IOException if an I/O exception occurs.
	 */
	public static String toString(URL url) throws IOException {
		return toString(url, null);
	}

	/**
	 * Gets the contents at the given URL.
	 * 
	 * @param url The URL source.
	 * @param encoding The encoding name for the URL contents.
	 * @return The contents of the URL as a String.
	 * @throws IOException if an I/O exception occurs.
	 */
	public static String toString(URL url, String encoding) throws IOException {
		InputStream inputStream = url.openStream();
		try {
			return toString(inputStream, encoding);
		}
		finally {
			inputStream.close();
		}
	}

	// readLines
	// -----------------------------------------------------------------------
	/**
	 * Get the contents of an <code>InputStream</code> as a list of Strings, one entry per line,
	 * using the default character encoding of the platform.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * 
	 * @param input the <code>InputStream</code> to read from, not null
	 * @return the list of Strings, never null
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static List<String> readLines(InputStream input) throws IOException {
		InputStreamReader reader = new InputStreamReader(input);
		return readLines(reader);
	}

	/**
	 * Get the contents of an <code>InputStream</code> as a list of Strings, one entry per line,
	 * using the specified character encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * 
	 * @param input the <code>InputStream</code> to read from, not null
	 * @param encoding the encoding to use, null means platform default
	 * @return the list of Strings, never null
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static List<String> readLines(InputStream input, String encoding) throws IOException {
		if (encoding == null) {
			return readLines(input);
		}
		else {
			InputStreamReader reader = new InputStreamReader(input, encoding);
			return readLines(reader);
		}
	}

	/**
	 * Get the contents of a <code>Reader</code> as a list of Strings, one entry per line.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * 
	 * @param input the <code>Reader</code> to read from, not null
	 * @return the list of Strings, never null
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static List<String> readLines(Reader input) throws IOException {
		BufferedReader reader = toBufferedReader(input);
		List<String> list = new ArrayList<String>();
		String line = reader.readLine();
		while (line != null) {
			list.add(line);
			line = reader.readLine();
		}
		return list;
	}

	// lineIterator
	// -----------------------------------------------------------------------
	/**
	 * Return an Iterator for the lines in a <code>Reader</code>.
	 * <p>
	 * <code>LineIterator</code> holds a reference to the open <code>Reader</code> specified here.
	 * When you have finished with the iterator you should close the reader to free internal
	 * resources. This can be done by closing the reader directly, or by calling
	 * {@link LineIterator#close()} or {@link LineIterator#safeClose(LineIterator)}.
	 * <p>
	 * The recommended usage pattern is:
	 * 
	 * <pre>
	 * try {
	 * 	LineIterator it = IOUtils.lineIterator(reader);
	 * 	while (it.hasNext()) {
	 * 		String line = it.nextLine();
	 * 		// / do something with line
	 * 	}
	 * }
	 * finally {
	 * 	IOUtils.closeQuietly(reader);
	 * }
	 * </pre>
	 * 
	 * @param reader the <code>Reader</code> to read from, not null
	 * @return an Iterator of the lines in the reader, never null
	 * @throws IllegalArgumentException if the reader is null
	 */
	public static LineIterator lineIterator(Reader reader) {
		return new LineIterator(reader);
	}

	/**
	 * Return an Iterator for the lines in an <code>InputStream</code>, using the character encoding
	 * specified (or default encoding if null).
	 * <p>
	 * <code>LineIterator</code> holds a reference to the open <code>InputStream</code> specified
	 * here. When you have finished with the iterator you should close the stream to free internal
	 * resources. This can be done by closing the stream directly, or by calling
	 * {@link LineIterator#close()} or {@link LineIterator#safeClose(LineIterator)}.
	 * <p>
	 * The recommended usage pattern is:
	 * 
	 * <pre>
	 * try {
	 * 	LineIterator it = IOUtils.lineIterator(stream, &quot;UTF-8&quot;);
	 * 	while (it.hasNext()) {
	 * 		String line = it.nextLine();
	 * 		// / do something with line
	 * 	}
	 * }
	 * finally {
	 * 	IOUtils.closeQuietly(stream);
	 * }
	 * </pre>
	 * 
	 * @param input the <code>InputStream</code> to read from, not null
	 * @param encoding the encoding to use, null means platform default
	 * @return an Iterator of the lines in the reader, never null
	 * @throws IllegalArgumentException if the input is null
	 * @throws IOException if an I/O error occurs, such as if the encoding is invalid
	 */
	public static LineIterator lineIterator(InputStream input, String encoding) throws IOException {
		Reader reader = null;
		if (encoding == null) {
			reader = new InputStreamReader(input);
		}
		else {
			reader = new InputStreamReader(input, encoding);
		}
		return new LineIterator(reader);
	}

	// -----------------------------------------------------------------------
	/**
	 * Convert the specified CharSequence to an input stream, encoded as bytes using the default
	 * character encoding of the platform.
	 * 
	 * @param input the CharSequence to convert
	 * @return an input stream
	 */
	public static InputStream toInputStream(CharSequence input) {
		return toInputStream(input.toString());
	}

	/**
	 * Convert the specified CharSequence to an input stream, encoded as bytes using the specified
	 * character encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * 
	 * @param input the CharSequence to convert
	 * @param encoding the encoding to use, null means platform default
	 * @throws IOException if the encoding is invalid
	 * @return an input stream
	 */
	public static InputStream toInputStream(CharSequence input, String encoding) throws IOException {
		return toInputStream(input.toString(), encoding);
	}

	// -----------------------------------------------------------------------
	/**
	 * Convert the specified string to an input stream, encoded as bytes using the default character
	 * encoding of the platform.
	 * 
	 * @param input the string to convert
	 * @return an input stream
	 * @since 1.1
	 */
	public static InputStream toInputStream(String input) {
		byte[] bytes = input.getBytes();
		return new ByteArrayInputStream(bytes);
	}

	/**
	 * Convert the specified string to an input stream, encoded as bytes using the specified
	 * character encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * 
	 * @param input the string to convert
	 * @param encoding the encoding to use, null means platform default
	 * @throws IOException if the encoding is invalid
	 * @return an input stream
	 */
	public static InputStream toInputStream(String input, String encoding) throws IOException {
		byte[] bytes = encoding != null ? input.getBytes(encoding) : input.getBytes();
		return new ByteArrayInputStream(bytes);
	}

	// write byte[]
	// -----------------------------------------------------------------------
	/**
	 * Writes bytes from a <code>byte[]</code> to an <code>OutputStream</code>.
	 * 
	 * @param data the byte array to write, do not modify during output, null ignored
	 * @param output the <code>OutputStream</code> to write to
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(byte[] data, OutputStream output) throws IOException {
		if (data != null) {
			output.write(data);
		}
	}

	/**
	 * Writes bytes from a <code>byte[]</code> to chars on a <code>Writer</code> using the default
	 * character encoding of the platform.
	 * <p>
	 * This method uses {@link String#String(byte[])}.
	 * 
	 * @param data the byte array to write, do not modify during output, null ignored
	 * @param output the <code>Writer</code> to write to
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(byte[] data, Appendable output) throws IOException {
		if (data != null) {
			output.append(new String(data));
		}
	}

	/**
	 * Writes bytes from a <code>byte[]</code> to chars on a <code>Writer</code> using the specified
	 * character encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * This method uses {@link String#String(byte[], String)}.
	 * 
	 * @param data the byte array to write, do not modify during output, null ignored
	 * @param output the <code>Writer</code> to write to
	 * @param encoding the encoding to use, null means platform default
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(byte[] data, Appendable output, String encoding) throws IOException {
		if (data != null) {
			if (encoding == null) {
				write(data, output);
			}
			else {
				output.append(new String(data, encoding));
			}
		}
	}

	// write char[]
	// -----------------------------------------------------------------------
	/**
	 * Writes chars from a <code>char[]</code> to a <code>Writer</code> using the default character
	 * encoding of the platform.
	 * 
	 * @param data the char array to write, do not modify during output, null ignored
	 * @param output the <code>Writer</code> to write to
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(char[] data, Appendable output) throws IOException {
		if (data != null) {
			CharSequence cs = CharSequences.toCharSequence(data);
			output.append(cs);
		}
	}

	/**
	 * Writes chars from a <code>char[]</code> to bytes on an <code>OutputStream</code>.
	 * <p>
	 * This method uses {@link String#String(char[])} and {@link String#getBytes()}.
	 * 
	 * @param data the char array to write, do not modify during output, null ignored
	 * @param output the <code>OutputStream</code> to write to
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(char[] data, OutputStream output) throws IOException {
		if (data != null) {
			output.write(new String(data).getBytes());
		}
	}

	/**
	 * Writes chars from a <code>char[]</code> to bytes on an <code>OutputStream</code> using the
	 * specified character encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * This method uses {@link String#String(char[])} and {@link String#getBytes(String)}.
	 * 
	 * @param data the char array to write, do not modify during output, null ignored
	 * @param output the <code>OutputStream</code> to write to
	 * @param encoding the encoding to use, null means platform default
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(char[] data, OutputStream output, String encoding) throws IOException {
		if (data != null) {
			if (encoding == null) {
				write(data, output);
			}
			else {
				output.write(new String(data).getBytes(encoding));
			}
		}
	}

	// write CharSequence
	// -----------------------------------------------------------------------
	/**
	 * Writes chars from a <code>CharSequence</code> to a <code>Writer</code>.
	 * 
	 * @param data the <code>CharSequence</code> to write, null ignored
	 * @param output the <code>Writer</code> to write to
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(CharSequence data, Appendable output) throws IOException {
		if (data != null) {
			output.append(data);
		}
	}

	/**
	 * Writes chars from a <code>CharSequence</code> to bytes on an <code>OutputStream</code> using
	 * the default character encoding of the platform.
	 * <p>
	 * This method uses {@link String#getBytes()}.
	 * 
	 * @param data the <code>CharSequence</code> to write, null ignored
	 * @param output the <code>OutputStream</code> to write to
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(CharSequence data, OutputStream output) throws IOException {
		if (data != null) {
			write(data.toString(), output);
		}
	}

	/**
	 * Writes chars from a <code>CharSequence</code> to bytes on an <code>OutputStream</code> using
	 * the specified character encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * This method uses {@link String#getBytes(String)}.
	 * 
	 * @param data the <code>CharSequence</code> to write, null ignored
	 * @param output the <code>OutputStream</code> to write to
	 * @param encoding the encoding to use, null means platform default
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(CharSequence data, OutputStream output, String encoding)
			throws IOException {
		if (data != null) {
			write(data.toString(), output, encoding);
		}
	}

	// write String
	// -----------------------------------------------------------------------
	/**
	 * Writes chars from a <code>String</code> to bytes on an <code>OutputStream</code> using the
	 * default character encoding of the platform.
	 * <p>
	 * This method uses {@link String#getBytes()}.
	 * 
	 * @param data the <code>String</code> to write, null ignored
	 * @param output the <code>OutputStream</code> to write to
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(String data, OutputStream output) throws IOException {
		if (data != null) {
			output.write(data.getBytes());
		}
	}

	/**
	 * Writes chars from a <code>String</code> to bytes on an <code>OutputStream</code> using the
	 * specified character encoding.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * This method uses {@link String#getBytes(String)}.
	 * 
	 * @param data the <code>String</code> to write, null ignored
	 * @param output the <code>OutputStream</code> to write to
	 * @param encoding the encoding to use, null means platform default
	 * @throws NullPointerException if output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void write(String data, OutputStream output, String encoding) throws IOException {
		if (data != null) {
			if (encoding == null) {
				write(data, output);
			}
			else {
				output.write(data.getBytes(encoding));
			}
		}
	}

	// writeLines
	// -----------------------------------------------------------------------
	/**
	 * Writes the <code>toString()</code> value of each item in a collection to an
	 * <code>OutputStream</code> line by line, using the default character encoding of the platform
	 * and the specified line ending.
	 * 
	 * @param lines the lines to write, null entries produce blank lines
	 * @param lineEnding the line separator to use, null is system default
	 * @param output the <code>OutputStream</code> to write to, not null, not closed
	 * @throws NullPointerException if the output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void writeLines(Collection<?> lines, String lineEnding, OutputStream output)
			throws IOException {
		if (lines == null) {
			return;
		}
		if (lineEnding == null) {
			lineEnding = LINE_SEPARATOR;
		}
		for (Object line : lines) {
			if (line != null) {
				output.write(line.toString().getBytes());
			}
			output.write(lineEnding.getBytes());
		}
	}

	/**
	 * Writes the <code>toString()</code> value of each item in a collection to an
	 * <code>OutputStream</code> line by line, using the specified character encoding and the
	 * specified line ending.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * 
	 * @param lines the lines to write, null entries produce blank lines
	 * @param lineEnding the line separator to use, null is system default
	 * @param output the <code>OutputStream</code> to write to, not null, not closed
	 * @param encoding the encoding to use, null means platform default
	 * @throws NullPointerException if the output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void writeLines(Collection<?> lines, String lineEnding, OutputStream output,
			String encoding) throws IOException {
		if (encoding == null) {
			writeLines(lines, lineEnding, output);
		}
		else {
			if (lines == null) {
				return;
			}
			if (lineEnding == null) {
				lineEnding = LINE_SEPARATOR;
			}
			for (Object line : lines) {
				if (line != null) {
					output.write(line.toString().getBytes(encoding));
				}
				output.write(lineEnding.getBytes(encoding));
			}
		}
	}

	/**
	 * Writes the <code>toString()</code> value of each item in a collection to a
	 * <code>Writer</code> line by line, using the specified line ending.
	 * 
	 * @param lines the lines to write, null entries produce blank lines
	 * @param lineEnding the line separator to use, null is system default
	 * @param writer the <code>Writer</code> to write to, not null, not closed
	 * @throws NullPointerException if the input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void writeLines(Collection<?> lines, String lineEnding, Appendable writer)
			throws IOException {
		if (lines == null) {
			return;
		}
		if (lineEnding == null) {
			lineEnding = LINE_SEPARATOR;
		}
		for (Object line : lines) {
			if (line != null) {
				writer.append(line.toString());
			}
			writer.append(lineEnding);
		}
	}

	// copy from InputStream
	// -----------------------------------------------------------------------
	/**
	 * Copy bytes from an <code>InputStream</code> to an <code>OutputStream</code>.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * <p>
	 * Large streams (over 2GB) will return a bytes copied value of <code>-1</code> after the copy
	 * has completed since the correct number of bytes cannot be returned as an int. For large
	 * streams use the <code>copyLarge(InputStream, OutputStream)</code> method.
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param output the <code>OutputStream</code> to write to
	 * @return the number of bytes copied, or -1 if &gt; Integer.MAX_VALUE
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 * @since 1.1
	 */
	public static int copy(InputStream input, OutputStream output) throws IOException {
		long count = copyLarge(input, output);
		if (count > Integer.MAX_VALUE) {
			return -1;
		}
		return (int)count;
	}

	/**
	 * Copy bytes from a large (over 2GB) <code>InputStream</code> to an <code>OutputStream</code>.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * <p>
	 * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param output the <code>OutputStream</code> to write to
	 * @return the number of bytes copied
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 * @since 1.3
	 */
	public static long copyLarge(InputStream input, OutputStream output) throws IOException {
		return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]);
	}

	/**
	 * Copy bytes from a large (over 2GB) <code>InputStream</code> to an <code>OutputStream</code>.
	 * <p>
	 * This method uses the provided buffer, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * <p>
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param output the <code>OutputStream</code> to write to
	 * @param buffer the buffer to use for the copy
	 * @return the number of bytes copied
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static long copyLarge(InputStream input, OutputStream output, byte[] buffer)
			throws IOException {
		long count = 0;
		int n = 0;
		while (EOF != (n = input.read(buffer))) {
			output.write(buffer, 0, n);
			count += n;
		}
		return count;
	}

	/**
	 * Copy some or all bytes from a large (over 2GB) <code>InputStream</code> to an
	 * <code>OutputStream</code>, optionally skipping input bytes.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * <p>
	 * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param output the <code>OutputStream</code> to write to
	 * @param inputOffset : number of bytes to skip from input before copying -ve values are ignored
	 * @param length : number of bytes to copy. -ve means all
	 * @return the number of bytes copied
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static long copyLarge(InputStream input, OutputStream output, long inputOffset,
			long length) throws IOException {
		return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]);
	}

	/**
	 * Copy some or all bytes from a large (over 2GB) <code>InputStream</code> to an
	 * <code>OutputStream</code>, optionally skipping input bytes.
	 * <p>
	 * This method uses the provided buffer, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * <p>
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param output the <code>OutputStream</code> to write to
	 * @param inputOffset : number of bytes to skip from input before copying -ve values are ignored
	 * @param length : number of bytes to copy. -ve means all
	 * @param buffer the buffer to use for the copy
	 * @return the number of bytes copied
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static long copyLarge(InputStream input, OutputStream output, final long inputOffset,
			final long length, byte[] buffer) throws IOException {
		if (inputOffset > 0) {
			skipFully(input, inputOffset);
		}
		if (length == 0) {
			return 0;
		}
		final int bufferLength = buffer.length;
		int bytesToRead = bufferLength;
		if (length > 0 && length < bufferLength) {
			bytesToRead = (int)length;
		}
		int read;
		long totalRead = 0;
		while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) {
			output.write(buffer, 0, read);
			totalRead += read;
			if (length > 0) { // only adjust length if not reading to the end
				// Note the cast must work because buffer.length is an integer
				bytesToRead = (int)Math.min(length - totalRead, bufferLength);
			}
		}
		return totalRead;
	}

	/**
	 * Copy bytes from an <code>InputStream</code> to chars on a <code>Writer</code> using the
	 * default character encoding of the platform.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * <p>
	 * This method uses {@link InputStreamReader}.
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param output the <code>Writer</code> to write to
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void copy(InputStream input, Appendable output) throws IOException {
		InputStreamReader in = new InputStreamReader(input);
		copy(in, output);
	}

	/**
	 * Copy bytes from an <code>InputStream</code> to chars on a <code>Writer</code> using the
	 * specified character encoding.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedInputStream</code>.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * This method uses {@link InputStreamReader}.
	 * 
	 * @param input the <code>InputStream</code> to read from
	 * @param output the <code>Writer</code> to write to
	 * @param encoding the encoding to use, null means platform default
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void copy(InputStream input, Appendable output, String encoding) throws IOException {
		if (encoding == null) {
			copy(input, output);
		}
		else {
			InputStreamReader in = new InputStreamReader(input, encoding);
			copy(in, output);
		}
	}

	// copy from Reader
	// -----------------------------------------------------------------------
	/**
	 * Copy chars from a <code>Reader</code> to a <code>Writer</code>.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * <p>
	 * Large streams (over 2GB) will return a chars copied value of <code>-1</code> after the copy
	 * has completed since the correct number of chars cannot be returned as an int. For large
	 * streams use the <code>copyLarge(Reader, Writer)</code> method.
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @param output the <code>Writer</code> to write to
	 * @return the number of characters copied, or -1 if &gt; Integer.MAX_VALUE
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static int copy(Reader input, Appendable output) throws IOException {
		long count = copyLarge(input, output);
		if (count > Integer.MAX_VALUE) {
			return -1;
		}
		return (int)count;
	}

	/**
	 * Copy chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * <p>
	 * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @param output the <code>Writer</code> to write to
	 * @return the number of characters copied
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static long copyLarge(Reader input, Appendable output) throws IOException {
		return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]);
	}

	/**
	 * Copy chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.
	 * <p>
	 * This method uses the provided buffer, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * <p>
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @param output the <code>Writer</code> to write to
	 * @param buffer the buffer to be used for the copy
	 * @return the number of characters copied
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static long copyLarge(Reader input, Appendable output, char[] buffer) throws IOException {
		long count = 0;
		int n = 0;
		while (EOF != (n = input.read(buffer))) {
			CharSequence cs = CharSequences.toCharSequence(buffer, 0, n);
			output.append(cs);
			count += n;
		}
		return count;
	}

	/**
	 * Copy some or all chars from a large (over 2GB) <code>InputStream</code> to an
	 * <code>OutputStream</code>, optionally skipping input chars.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * <p>
	 * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @param output the <code>Writer</code> to write to
	 * @param inputOffset : number of chars to skip from input before copying -ve values are ignored
	 * @param length : number of chars to copy. -ve means all
	 * @return the number of chars copied
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static long copyLarge(Reader input, Appendable output, final long inputOffset,
			final long length) throws IOException {
		return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]);
	}

	/**
	 * Copy some or all chars from a large (over 2GB) <code>InputStream</code> to an
	 * <code>OutputStream</code>, optionally skipping input chars.
	 * <p>
	 * This method uses the provided buffer, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * <p>
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @param output the <code>Writer</code> to write to
	 * @param inputOffset : number of chars to skip from input before copying -ve values are ignored
	 * @param length : number of chars to copy. -ve means all
	 * @param buffer the buffer to be used for the copy
	 * @return the number of chars copied
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static long copyLarge(Reader input, Appendable output, final long inputOffset,
			final long length, char[] buffer) throws IOException {
		if (inputOffset > 0) {
			skipFully(input, inputOffset);
		}
		if (length == 0) {
			return 0;
		}
		int bytesToRead = buffer.length;
		if (length > 0 && length < buffer.length) {
			bytesToRead = (int)length;
		}
		int read;
		long totalRead = 0;
		while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) {
			CharSequence cs = CharSequences.toCharSequence(buffer, 0, read);
			output.append(cs);
			totalRead += read;
			if (length > 0) { // only adjust length if not reading to the end
				// Note the cast must work because buffer.length is an integer
				bytesToRead = (int)Math.min(length - totalRead, buffer.length);
			}
		}
		return totalRead;
	}

	/**
	 * Copy chars from a <code>Reader</code> to bytes on an <code>OutputStream</code> using the
	 * default character encoding of the platform, and calling flush.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * <p>
	 * Due to the implementation of OutputStreamWriter, this method performs a flush.
	 * <p>
	 * This method uses {@link OutputStreamWriter}.
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @param output the <code>OutputStream</code> to write to
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void copy(Reader input, OutputStream output) throws IOException {
		OutputStreamWriter out = new OutputStreamWriter(output);
		copy(input, out);
		// XXX Unless anyone is planning on rewriting OutputStreamWriter, we
		// have to flush here.
		out.flush();
	}

	/**
	 * Copy chars from a <code>Reader</code> to bytes on an <code>OutputStream</code> using the
	 * specified character encoding, and calling flush.
	 * <p>
	 * This method buffers the input internally, so there is no need to use a
	 * <code>BufferedReader</code>.
	 * <p>
	 * Character encoding names can be found at <a
	 * href="http://www.iana.org/assignments/character-sets">IANA</a>.
	 * <p>
	 * Due to the implementation of OutputStreamWriter, this method performs a flush.
	 * <p>
	 * This method uses {@link OutputStreamWriter}.
	 * 
	 * @param input the <code>Reader</code> to read from
	 * @param output the <code>OutputStream</code> to write to
	 * @param encoding the encoding to use, null means platform default
	 * @throws NullPointerException if the input or output is null
	 * @throws IOException if an I/O error occurs
	 */
	public static void copy(Reader input, OutputStream output, String encoding) throws IOException {
		if (encoding == null) {
			copy(input, output);
		}
		else {
			OutputStreamWriter out = new OutputStreamWriter(output, encoding);
			copy(input, out);
			// XXX Unless anyone is planning on rewriting OutputStreamWriter,
			// we have to flush here.
			out.flush();
		}
	}

	// content equals
	// -----------------------------------------------------------------------
	/**
	 * Compare the contents of two Streams to determine if they are equal or not.
	 * <p>
	 * This method buffers the input internally using <code>BufferedInputStream</code> if they are
	 * not already buffered.
	 * 
	 * @param input1 the first stream
	 * @param input2 the second stream
	 * @return true if the content of the streams are equal or they both don't exist, false
	 *         otherwise
	 * @throws NullPointerException if either input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static boolean contentEquals(InputStream input1, InputStream input2) throws IOException {
		if (!(input1 instanceof BufferedInputStream)) {
			input1 = new BufferedInputStream(input1);
		}
		if (!(input2 instanceof BufferedInputStream)) {
			input2 = new BufferedInputStream(input2);
		}

		int ch = input1.read();
		while (EOF != ch) {
			int ch2 = input2.read();
			if (ch != ch2) {
				return false;
			}
			ch = input1.read();
		}

		int ch2 = input2.read();
		return ch2 == EOF;
	}

	/**
	 * Compare the contents of two Readers to determine if they are equal or not.
	 * <p>
	 * This method buffers the input internally using <code>BufferedReader</code> if they are not
	 * already buffered.
	 * 
	 * @param input1 the first reader
	 * @param input2 the second reader
	 * @return true if the content of the readers are equal or they both don't exist, false
	 *         otherwise
	 * @throws NullPointerException if either input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static boolean contentEquals(Reader input1, Reader input2) throws IOException {

		input1 = toBufferedReader(input1);
		input2 = toBufferedReader(input2);

		int ch = input1.read();
		while (EOF != ch) {
			int ch2 = input2.read();
			if (ch != ch2) {
				return false;
			}
			ch = input1.read();
		}

		int ch2 = input2.read();
		return ch2 == EOF;
	}

	/**
	 * Compare the contents of two Readers to determine if they are equal or not, ignoring EOL
	 * characters.
	 * <p>
	 * This method buffers the input internally using <code>BufferedReader</code> if they are not
	 * already buffered.
	 * 
	 * @param input1 the first reader
	 * @param input2 the second reader
	 * @return true if the content of the readers are equal (ignoring EOL differences), false
	 *         otherwise
	 * @throws NullPointerException if either input is null
	 * @throws IOException if an I/O error occurs
	 */
	public static boolean contentEqualsIgnoreEOL(Reader input1, Reader input2) throws IOException {
		BufferedReader br1 = toBufferedReader(input1);
		BufferedReader br2 = toBufferedReader(input2);

		String line1 = br1.readLine();
		String line2 = br2.readLine();
		while (line1 != null && line2 != null && line1.equals(line2)) {
			line1 = br1.readLine();
			line2 = br2.readLine();
		}
		return line1 == null ? line2 == null ? true : false : line1.equals(line2);
	}

	public static long available(InputStream input) throws IOException {
		return input.available();
	}
	
	public static long available(Reader reader) throws IOException {
		if (reader.markSupported()) {
			reader.mark(Integer.MAX_VALUE);
			long len = reader.skip(Integer.MAX_VALUE);
			reader.reset();
			return len;
		}
		return -1;
	}
	
	/**
	 * Skip bytes from an input byte stream. This implementation guarantees that it will read as
	 * many bytes as possible before giving up; this may not always be the case for subclasses of
	 * {@link Reader}.
	 * 
	 * @param input byte stream to skip
	 * @param toSkip number of bytes to skip.
	 * @return number of bytes actually skipped.
	 * @see InputStream#skip(long)
	 * @throws IOException if there is a problem reading the file
	 * @throws IllegalArgumentException if toSkip is negative
	 */
	public static long skip(InputStream input, long toSkip) throws IOException {
		if (toSkip < 0) {
			throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip);
		}
		/*
		 * N.B. no need to synchronize this because: - we don't care if the buffer is created
		 * multiple times (the data is ignored) - we always use the same size buffer, so if it it is
		 * recreated it will still be OK (if the buffer size were variable, we would need to synch.
		 * to ensure some other thread did not create a smaller one)
		 */
		if (SKIP_BYTE_BUFFER == null) {
			SKIP_BYTE_BUFFER = new byte[SKIP_BUFFER_SIZE];
		}
		long remain = toSkip;
		while (remain > 0) {
			long n = input.read(SKIP_BYTE_BUFFER, 0, (int)Math.min(remain, SKIP_BUFFER_SIZE));
			if (n < 0) { // EOF
				break;
			}
			remain -= n;
		}
		return toSkip - remain;
	}

	/**
	 * Skip characters from an input character stream. This implementation guarantees that it will
	 * read as many characters as possible before giving up; this may not always be the case for
	 * subclasses of {@link Reader}.
	 * 
	 * @param input character stream to skip
	 * @param toSkip number of characters to skip.
	 * @return number of characters actually skipped.
	 * @see Reader#skip(long)
	 * @throws IOException if there is a problem reading the file
	 * @throws IllegalArgumentException if toSkip is negative
	 */
	public static long skip(Reader input, long toSkip) throws IOException {
		if (toSkip < 0) {
			throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip);
		}
		/*
		 * N.B. no need to synchronize this because: - we don't care if the buffer is created
		 * multiple times (the data is ignored) - we always use the same size buffer, so if it it is
		 * recreated it will still be OK (if the buffer size were variable, we would need to synch.
		 * to ensure some other thread did not create a smaller one)
		 */
		if (SKIP_CHAR_BUFFER == null) {
			SKIP_CHAR_BUFFER = new char[SKIP_BUFFER_SIZE];
		}
		long remain = toSkip;
		while (remain > 0) {
			long n = input.read(SKIP_CHAR_BUFFER, 0, (int)Math.min(remain, SKIP_BUFFER_SIZE));
			if (n < 0) { // EOF
				break;
			}
			remain -= n;
		}
		return toSkip - remain;
	}

	/**
	 * Skip the requested number of bytes or fail if there are not enough left.
	 * <p>
	 * This allows for the possibility that {@link InputStream#skip(long)} may not skip as many
	 * bytes as requested (most likely because of reaching EOF).
	 * 
	 * @param input stream to skip
	 * @param toSkip the number of bytes to skip
	 * @see InputStream#skip(long)
	 * @throws IOException if there is a problem reading the file
	 * @throws IllegalArgumentException if toSkip is negative
	 * @throws EOFException if the number of bytes skipped was incorrect
	 */
	public static void skipFully(InputStream input, long toSkip) throws IOException {
		if (toSkip < 0) {
			throw new IllegalArgumentException("Bytes to skip must not be negative: " + toSkip);
		}
		long skipped = skip(input, toSkip);
		if (skipped != toSkip) {
			throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped);
		}
	}

	/**
	 * Skip the requested number of characters or fail if there are not enough left.
	 * <p>
	 * This allows for the possibility that {@link Reader#skip(long)} may not skip as many
	 * characters as requested (most likely because of reaching EOF).
	 * 
	 * @param input stream to skip
	 * @param toSkip the number of characters to skip
	 * @see Reader#skip(long)
	 * @throws IOException if there is a problem reading the file
	 * @throws IllegalArgumentException if toSkip is negative
	 * @throws EOFException if the number of characters skipped was incorrect
	 */
	public static void skipFully(Reader input, long toSkip) throws IOException {
		long skipped = skip(input, toSkip);
		if (skipped != toSkip) {
			throw new EOFException("Chars to skip: " + toSkip + " actual: " + skipped);
		}
	}

	/**
	 * Read characters from an input character stream. This implementation guarantees that it will
	 * read as many characters as possible before giving up; this may not always be the case for
	 * subclasses of {@link Reader}.
	 * 
	 * @param input where to read input from
	 * @param buffer destination
	 * @param offset inital offset into buffer
	 * @param length length to read, must be >= 0
	 * @return actual length read; may be less than requested if EOF was reached
	 * @throws IOException if a read error occurs
	 */
	public static int read(Reader input, char[] buffer, int offset, int length) throws IOException {
		if (length < 0) {
			throw new IllegalArgumentException("Length must not be negative: " + length);
		}
		int remaining = length;
		while (remaining > 0) {
			int location = length - remaining;
			int count = input.read(buffer, offset + location, remaining);
			if (EOF == count) { // EOF
				break;
			}
			remaining -= count;
		}
		return length - remaining;
	}

	/**
	 * Read characters from an input character stream. This implementation guarantees that it will
	 * read as many characters as possible before giving up; this may not always be the case for
	 * subclasses of {@link Reader}.
	 * 
	 * @param input where to read input from
	 * @param buffer destination
	 * @return actual length read; may be less than requested if EOF was reached
	 * @throws IOException if a read error occurs
	 * @since 2.2
	 */
	public static int read(Reader input, char[] buffer) throws IOException {
		return read(input, buffer, 0, buffer.length);
	}

	/**
	 * Read bytes from an input stream. This implementation guarantees that it will read as many
	 * bytes as possible before giving up; this may not always be the case for subclasses of
	 * {@link InputStream}.
	 * 
	 * @param input where to read input from
	 * @param buffer destination
	 * @param offset inital offset into buffer
	 * @param length length to read, must be >= 0
	 * @return actual length read; may be less than requested if EOF was reached
	 * @throws IOException if a read error occurs
	 */
	public static int read(InputStream input, byte[] buffer, int offset, int length)
			throws IOException {
		if (length < 0) {
			throw new IllegalArgumentException("Length must not be negative: " + length);
		}
		int remaining = length;
		while (remaining > 0) {
			int location = length - remaining;
			int count = input.read(buffer, offset + location, remaining);
			if (EOF == count) { // EOF
				break;
			}
			remaining -= count;
		}
		return length - remaining;
	}

	/**
	 * Read bytes from an input stream. This implementation guarantees that it will read as many
	 * bytes as possible before giving up; this may not always be the case for subclasses of
	 * {@link InputStream}.
	 * 
	 * @param input where to read input from
	 * @param buffer destination
	 * @return actual length read; may be less than requested if EOF was reached
	 * @throws IOException if a read error occurs
	 */
	public static int read(InputStream input, byte[] buffer) throws IOException {
		return read(input, buffer, 0, buffer.length);
	}

	/**
	 * Read the requested number of characters or fail if there are not enough left.
	 * <p>
	 * This allows for the possibility that {@link Reader#read(char[], int, int)} may not read as
	 * many characters as requested (most likely because of reaching EOF).
	 * 
	 * @param input where to read input from
	 * @param buffer destination
	 * @param offset inital offset into buffer
	 * @param length length to read, must be >= 0
	 * @throws IOException if there is a problem reading the file
	 * @throws IllegalArgumentException if length is negative
	 * @throws EOFException if the number of characters read was incorrect
	 */
	public static void readFully(Reader input, char[] buffer, int offset, int length)
			throws IOException {
		int actual = read(input, buffer, offset, length);
		if (actual != length) {
			throw new EOFException("Length to read: " + length + " actual: " + actual);
		}
	}

	/**
	 * Read the requested number of characters or fail if there are not enough left.
	 * <p>
	 * This allows for the possibility that {@link Reader#read(char[], int, int)} may not read as
	 * many characters as requested (most likely because of reaching EOF).
	 * 
	 * @param input where to read input from
	 * @param buffer destination
	 * @throws IOException if there is a problem reading the file
	 * @throws IllegalArgumentException if length is negative
	 * @throws EOFException if the number of characters read was incorrect
	 */
	public static void readFully(Reader input, char[] buffer) throws IOException {
		readFully(input, buffer, 0, buffer.length);
	}

	/**
	 * Read the requested number of bytes or fail if there are not enough left.
	 * <p>
	 * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may not read
	 * as many bytes as requested (most likely because of reaching EOF).
	 * 
	 * @param input where to read input from
	 * @param buffer destination
	 * @param offset inital offset into buffer
	 * @param length length to read, must be >= 0
	 * @throws IOException if there is a problem reading the file
	 * @throws IllegalArgumentException if length is negative
	 * @throws EOFException if the number of bytes read was incorrect
	 */
	public static void readFully(InputStream input, byte[] buffer, int offset, int length)
			throws IOException {
		int actual = read(input, buffer, offset, length);
		if (actual != length) {
			throw new EOFException("Length to read: " + length + " actual: " + actual);
		}
	}

	/**
	 * Read the requested number of bytes or fail if there are not enough left.
	 * <p>
	 * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may not read
	 * as many bytes as requested (most likely because of reaching EOF).
	 * 
	 * @param input where to read input from
	 * @param buffer destination
	 * @throws IOException if there is a problem reading the file
	 * @throws IllegalArgumentException if length is negative
	 * @throws EOFException if the number of bytes read was incorrect
	 */
	public static void readFully(InputStream input, byte[] buffer) throws IOException {
		readFully(input, buffer, 0, buffer.length);
	}

	/**
	 * A empty InputStream
	 */
	private static class EmptyInputStream extends InputStream {
		public int read() throws IOException {
			return EOF;
		}
	}
	
	public static final InputStream EMPTY_INPUT_STREAM = new EmptyInputStream();

	public static InputStream emptyInputStream() {
		return EMPTY_INPUT_STREAM;
	}

	/**
	 * 为一个输入流包裹一个缓冲流。如果这个输入流本身就是缓冲流，则直接返回
	 * 
	 * @param ins 输入流。
	 * @return 缓冲输入流
	 */
	public static BufferedInputStream buff(InputStream ins) {
		Asserts.notNull(ins);

		if (ins instanceof BufferedInputStream) {
			return (BufferedInputStream)ins;
		}
		
		// BufferedInputStream的构造方法,竟然是允许null参数的!! 我&$#^$&%
		return new BufferedInputStream(ins);
	}

	/**
	 * 为一个输出流包裹一个缓冲流。如果这个输出流本身就是缓冲流，则直接返回
	 * 
	 * @param ops 输出流。
	 * @return 缓冲输出流
	 */
	public static BufferedOutputStream buff(OutputStream ops) {
		Asserts.notNull(ops);

		if (ops instanceof BufferedOutputStream) {
			return (BufferedOutputStream)ops;
		}
		return new BufferedOutputStream(ops);
	}

	/**
	 * 为一个文本输入流包裹一个缓冲流。如果这个输入流本身就是缓冲流，则直接返回
	 * 
	 * @param reader 文本输入流。
	 * @return 缓冲文本输入流
	 */
	public static BufferedReader buffr(Reader reader) {
		if (reader instanceof BufferedReader) {
			return (BufferedReader)reader;
		}
		return new BufferedReader(reader);
	}

	/**
	 * 为一个文本输出流包裹一个缓冲流。如果这个文本输出流本身就是缓冲流，则直接返回
	 * 
	 * @param writer 文本输出流。
	 * @return 缓冲文本输出流
	 */
	public static BufferedWriter buffw(Writer writer) {
		if (writer instanceof BufferedWriter) {
			return (BufferedWriter)writer;
		}
		return new BufferedWriter(writer);
	}
}
